﻿using System;
using System.IO;
using System.Text;

namespace FLHook.Core
{
    /// <summary>
    /// Freelancer .FL Savegame encode/decoder
    /// </summary>
    /// <remarks>
    /// Credits to Sherlog [sherlog@t-online.de] for finding out the algorithm
    /// 
    /// (c) 2003 by Jor flcodec@jors.net
    /// 
    /// This is free software. Permission to copy, store and use granted as long
    /// as this copyright note remains intact.
    /// 
    /// Compilation in a POSIX environment:
    /// 
    ///    cc -O -o flcodec flcodec.c
    /// 
    /// Or in Wintendo 32 (get the free lcc compiler):
    /// 
    ///    lcc -O flcodec.c
    ///    lcclnk -o flcodec.exe flcodec.obj
    /// </remarks>
    public static class FLCodec
    {
        // Very Secret Key - this is Microsoft Security In Action[tm]
        private const string MagicToken = "FLS1";
        private const int MagicTokenByteCount = 4;
        private static readonly byte[] MagicTokenBytes = Encoding.ASCII.GetBytes(MagicToken);
        private const string Gene = "Gene";
        private const int GeneByteCount = 4;
        private static readonly byte[] GeneBytes = Encoding.ASCII.GetBytes(Gene);

        public static bool Decode(string inputFile, string outputFile)
        {
            if (string.IsNullOrEmpty(inputFile))
                throw new ArgumentNullException("inputFile");
            if (string.IsNullOrEmpty(outputFile))
                throw new ArgumentNullException("outputFile");
            using (var inputStream = File.Open(inputFile, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                if (inputStream.Length > int.MaxValue)
                    throw new InvalidOperationException("The file is too large.");
                const int bufferSize = 1024 * 4;
                var buffer = new byte[bufferSize];
                var readLength = inputStream.Read(buffer, 0, MagicTokenByteCount);
                if (readLength != MagicTokenByteCount)
                    return false;
                var encoding = Encoding.ASCII;
                var magicToken = encoding.GetChars(buffer, 0, MagicTokenByteCount);
                if (magicToken.Length != MagicToken.Length)
                    return false;
                for (var i = 0; i < MagicToken.Length; i++)
                {
                    if (magicToken[i] != MagicToken[i])
                        return false;
                }
                using (var outputStream = File.Open(outputFile, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    var i = 0;
                    while ((readLength = inputStream.Read(buffer, 0, bufferSize)) > 0)
                    {
                        for (var index = 0; index < readLength; index++)
                        {
                            var c = buffer[index];
                            var k = (GeneBytes[i % GeneByteCount] + i) % 256;
                            var r = c ^ (k | 0x80);
                            buffer[index] = (byte)r;
                            i++;
                        }
                        outputStream.Write(buffer, 0, readLength);
                    }
                }
            }
            return true;
        }

        public static bool Encode(string inputFile, string outputFile)
        {
            if (string.IsNullOrEmpty(inputFile))
                throw new ArgumentNullException("inputFile");
            if (string.IsNullOrEmpty(outputFile))
                throw new ArgumentNullException("outputFile");
            using (var inputStream = File.Open(inputFile, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                if (inputStream.Length > int.MaxValue)
                    throw new InvalidOperationException("The file is too large.");
                const int bufferSize = 1024 * 4;
                var buffer = new byte[bufferSize];
                using (var outputStream = File.Open(outputFile, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    outputStream.Write(MagicTokenBytes, 0, MagicTokenByteCount);
                    var i = 0;
                    int readLength;
                    while ((readLength = inputStream.Read(buffer, 0, bufferSize)) > 0)
                    {
                        for (var index = 0; index < readLength; index++)
                        {
                            var c = buffer[index];
                            var k = (GeneBytes[i % GeneByteCount] + i) % 256;
                            var r = c ^ (k | 0x80);
                            buffer[index] = (byte)r;
                            i++;
                        }
                        outputStream.Write(buffer, 0, readLength);
                    }
                }
            }
            return true;
        }

    }

}
